/*****************************************************************************
 *   Copyright 2011 ZHUANG Yuan              	                             *
 *   SudokuITS as Final Year Project for BScCS, City University of Hong Kong *
 *                                      									 *
 *   Licensed under the Apache License, Version 2.0 (the "License");         *
 *   you may not use this file except in compliance with the License.        *
 *   You may obtain a copy of the License at                                 *
 *                                                                           *
 *   http://www.apache.org/licenses/LICENSE-2.0                              *
 *                                                                           *
 *   Unless required by applicable law or agreed to in writing, software     *
 *   distributed under the License is distributed on an "AS IS" BASIS,       *
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*
 *   See the License for the specific language governing permissions and     *
 *   limitations under the License.                                          *
 *****************************************************************************/

package android.sudoku.its.views;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.sudoku.its.expert.Expert;
import android.sudoku.its.mysql.MySQLHandler;
import android.sudoku.its.sqlite.SQLiteHandler;
import android.sudoku.its.sqlite.StudentTable;
import android.sudoku.its.student.Student;
import android.sudoku.its.tutor.Tutor;
import android.sudoku.its.util.GamePageUtil;
import android.sudoku.its.util.GameVariables;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ToggleButton;

/**
 * Class for a Sudoku game
 * @author 51158282
 *
 */

public class GamePage extends Activity implements OnClickListener{
	protected static final String DIFF_LEVEL = "Game Difficulty Level";
	public static final String RESUME_THE_GAME = "Resume the game";
	private static final String RESUME_PUZZLE = "Resume a Puzzle";
	private static final String RESUME_PENCILMARK = "Resume Pencil Marks";
	private static final String PRE_N_ROW = "Previously Seleceted Row";
	private static final String PRE_N_COL = "Previously Selected Column";
	private static final String PRE_DIFF_LEVEL = "Previously Puzzle Difficulty Level";
	private static final String PRE_TIME_USED = "Previously Used Time";
	public static final int NULL_DIFFICULT_LEVEL_INDICATOR = -1;
	public static final int NOT_RESUME_INDICATOR = 1;
	public static final int RESUME_INDICATOR = 0;
	private static final int SYSTEM_IDLE_THRESHOLD = 600000; // 10 minutes
	private final long DEFAULT_USED_TIME = 0;
	private final int DEFAULT_PRE_N_ROW = -1;
	private final int DEFAULT_PRE_N_COL = -1;
	private final String DEFAULT_PUZZLE = 
			"360000000004230800000004200" + 
			"070460003820000014500013020" + 
			"001900000007048300000000045";
	
	private GameBoard gameBoard;
	private GamePageUtil gamePageUtil;		
	private TextView instructionBox;
	private int diffLevel;
	private long timeUsed;
	private int ifResumeIndicator;
	private Expert expert;
	private Student student;
	private Tutor tutor;
	SQLiteHandler sqliteHandler;
	MySQLHandler mySQLHandler;
	StudentTable studentTable;
	private boolean isPencilMarkModeOn = false;		// if true, input pencil mark; else input number
	private int[] clues = new int[GameVariables.PUZZLE_LENGTH]; // the puzzle
	private int[] puzzleSolution = new int[GameVariables.PUZZLE_LENGTH]; // the solution of the puzzle
	private int[][] pencilmarks = new int[GameVariables.PUZZLE_LENGTH][GameVariables.HOUSE_SIZE];
	
	private final String PRIORITIZED_STRATEGY_KEY = "prioStrategy";
	private final String LANGUAGE_PREF_KEY = "languagePref";
	private String langPref;	
	private boolean isStudent = false;
	private Handler timerHandler;
	private long autoInstrTimeInterval;
	private Runnable autoInstrTask;
	private Runnable timerDisplayTask;
	private Runnable stopAutoInstrTask;
	private long gameStartTime;
	private TextView timerDisplayArea;	
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);		
		gamePageUtil = new GamePageUtil();	
        
		// Initialize time used variable
		timeUsed = 0;
		
		// Initialize timer handler
		timerHandler = new Handler();
		
		// Define timer display task
		timerDisplayTask = new Runnable() {
			public void run() {
				updateDisplayTimer();
				}
			};	
		
		// Define auto-instruction task
		autoInstrTask = new Runnable() {
			public void run(){
				giveAutoInstr();
				}
			};
			
		// Define the task to stop auto-instruction if the system has been idle for too long
			stopAutoInstrTask = new Runnable() {
			public void run(){
				stopAutoInstr();
			}
		};
		
        // Get instruction language preference. Default value is EN.
		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
		langPref = prefs.getString(LANGUAGE_PREF_KEY, GameVariables.LANG_ENGLISH);		
		
		// Get strategy priority. Default value is full_house
		int prioStrategy = Integer.parseInt(prefs.getString(PRIORITIZED_STRATEGY_KEY, Integer.toString(GameVariables.FULL_HOUSE_INDEX)));
		
		
		// Get user title which is either the student's name meaning that this is a logged-in or registered student or "guest" meaning that this is a guest
		String userTitle = getIntent().getStringExtra(MainMenuPage.USER_TITLE);
		if (!userTitle.equals(GameVariables.USER_TITLE_GUEST)) {				
			isStudent = true;			
			// If this is a registered student, get his/her information from database
			sqliteHandler = new SQLiteHandler(this);
			studentTable = new StudentTable();
			student = studentTable.getStudentByName(sqliteHandler, userTitle);
		}	
		else {
			// If this is a guest user, create a temporary student object for him/her. 
			// This object will not be stored into database when the guest quit the system
			student = new Student();
		}
		
		ifResumeIndicator = getIntent().getIntExtra(RESUME_THE_GAME, NOT_RESUME_INDICATOR);
		
		diffLevel = getIntent().getIntExtra(DIFF_LEVEL, GameVariables.DIFF_LEVEL_BEGINNER);
		clues = gamePageUtil.convertClueFromStringToIntArray(getPuzzleOfDiff(diffLevel));		
		pencilmarks = getPencilmarks(diffLevel);		
		
		expert = new Expert(gamePageUtil.getTwoDClues(clues));		// initialize expert model
		
		int preNRow = -1;
		int preNCol = -1;
		
		// If this game is resumed from previous game
		if (ifResumeIndicator == RESUME_INDICATOR) {
			SharedPreferences pref = getPreferences(MODE_PRIVATE);
			preNRow = pref.getInt(PRE_N_ROW, DEFAULT_PRE_N_ROW);
			preNCol = pref.getInt(PRE_N_COL, DEFAULT_PRE_N_COL);
			diffLevel = pref.getInt(PRE_DIFF_LEVEL, GameVariables.DIFF_LEVEL_BEGINNER);
			timeUsed = pref.getLong(PRE_TIME_USED, DEFAULT_USED_TIME);
		}
		
		setContentView(R.layout.game);
				
		// Add user title to the right bottom corner of the interface
        /*
		TextView userTitleDisplay = (TextView) findViewById(R.id.accessory_displays);
        userTitleDisplay.setText(userTitle);
        */
		
		timerDisplayArea = (TextView) findViewById(R.id.timer_display);
		
		// Add puzzle view to the game page
        LinearLayout gameBoardContainer = (LinearLayout) this.findViewById(R.id.gameboard_container);
        gameBoard = new GameBoard(this, clues, pencilmarks, preNRow, preNCol);
        gameBoard.requestFocus();
        gameBoardContainer.addView(gameBoard);
        
        // Set onClickListner for number buttons
        View numberButton1 = findViewById(R.id.number_button_1);
        numberButton1.setOnClickListener(this);
        View numberButton2 = findViewById(R.id.number_button_2);
        numberButton2.setOnClickListener(this);
        View numberButton3 = findViewById(R.id.number_button_3);
        numberButton3.setOnClickListener(this);
        View numberButton4 = findViewById(R.id.number_button_4);
        numberButton4.setOnClickListener(this);
        View numberButton5 = findViewById(R.id.number_button_5);
        numberButton5.setOnClickListener(this);
        View numberButton6 = findViewById(R.id.number_button_6);
        numberButton6.setOnClickListener(this);
        View numberButton7 = findViewById(R.id.number_button_7);
        numberButton7.setOnClickListener(this);
        View numberButton8 = findViewById(R.id.number_button_8);
        numberButton8.setOnClickListener(this);
        View numberButton9 = findViewById(R.id.number_button_9);
        numberButton9.setOnClickListener(this);
        
        // Set onClickListner for accessory buttons
        View pencilMarkButton = findViewById(R.id.pencil_mark_button);
        pencilMarkButton.setOnClickListener(this);
        View hintButton = findViewById(R.id.hint_button);
        hintButton.setOnClickListener(this);  
        
                
/*        View testCgButton = findViewById(R.id.testCm_button);
        testCgButton.setOnClickListener(this);
        
        View testAsmButton = findViewById(R.id.testAsm_button);
        testAsmButton.setOnClickListener(this);  */     
        
        // Initialize instruction box
        instructionBox = (TextView) findViewById(R.id.instruction_box);
        instructionBox.setMovementMethod(new ScrollingMovementMethod());
        // If the current user is a naive user, display Sudoku rules in instruction box and immediately upgrade his/her level to beginner
        if (student.getLevel() == GameVariables.LEVEL_NAIVE){
        	student.setLevel(GameVariables.LEVEL_BEGINNER);
        	String sudokuRule = getResources().getString(R.string.sudoku_rule);
        	outputInstruction(sudokuRule);      	
        }
        
        // Initialize tutor model
		tutor = new Tutor(this, langPref, prioStrategy, student.getLevel());		 
        
		// Initialize auto-instruction time interval
		autoInstrTimeInterval = gamePageUtil.getAutoInstrTimeInterval(diffLevel, student.getLevel());
        
        // Display timer at the right bottom corner
        displayTimer(timeUsed);        
        
        // Calculate puzzle solution
        puzzleSolution = gamePageUtil.generatePuzzleSolution(this, clues);
        
        // Set timer for auto-instruction
        setAutoInstrTimer();     
        
        // Set a detector to detect system idle time 
        // and stop auto-instruction if the system has been idle for too long 
        setSystemIdleDetector();
        
        // If the activity is restarted, do a continue next time
        getIntent().putExtra(DIFF_LEVEL, RESUME_PUZZLE);              
	}
	
	@Override
	protected void onPause() {
		super.onPause();
		
		// Remove all timer handler tasks in queue
		removeAllTimeHandlerTasks();
		
		// Save current game state
		SharedPreferences.Editor pref = getPreferences(MODE_PRIVATE).edit();
		pref.putString(RESUME_PUZZLE, gamePageUtil.convertClueFromIntToString(clues)).commit(); // save clues
		pref.putString(RESUME_PENCILMARK, gamePageUtil.convertPencilmarkFromIntToString(pencilmarks)).commit(); // save pencil marks
		pref.putInt(PRE_N_ROW, gameBoard.getNRow()).commit();	// save row index of of selected cell
		pref.putInt(PRE_N_COL, gameBoard.getNCol()).commit();	// save column index of of selected cell
		pref.putInt(PRE_DIFF_LEVEL, diffLevel).commit();	// save puzzle difficulty level
		pref.putLong(PRE_TIME_USED, timeUsed).commit();		// save used time
		
		// If the current user is a student, save his/her performance to SQLite database
		if (isStudent) {
			saveToSQLite();
		}
	}
	
	
	public void onClick(View v) {	
		// Reset auto-instruction timer
		setAutoInstrTimer();
		
		// Reset system idle detector
		setSystemIdleDetector();
		
		switch(v.getId()) {
		case R.id.number_button_1:
			handleInputNumber(1);
			break;
		case R.id.number_button_2:
			handleInputNumber(2);
			break;		
		case R.id.number_button_3:
			handleInputNumber(3);
			break;
		case R.id.number_button_4:
			handleInputNumber(4);
			break;	
		case R.id.number_button_5:
			handleInputNumber(5);
			break;
		case R.id.number_button_6:
			handleInputNumber(6);
			break;		
		case R.id.number_button_7:
			handleInputNumber(7);
			break;
		case R.id.number_button_8:
			handleInputNumber(8);
			break;
		case R.id.number_button_9:
			handleInputNumber(9);
			break;			
		case R.id.pencil_mark_button:
			changePencilMarkMode(((ToggleButton)v).isChecked());
			break;
		case R.id.hint_button:
			outputInstruction(tutor.giveHint(this, gamePageUtil.getTwoDClues(clues)));
			break;
/*		case R.id.testCm_button: // TODO delete this is a test function upon finishing
			gameBoard.redrawPencilmarks(expert.getCm());
			break;
		case R.id.testAsm_button: // TODO delete this is a test function upon finishing
			gameBoard.redrawPencilmarks(expert.getAsm());
			break;*/
		}			
		
	}

	/**
	 * Handle number input or pencil mark input
	 * @param num		an integer indicating the number to be draw on the game board
	 */
	private void handleInputNumber(int num) {
		// Get current selected cell
		int nRow = gameBoard.getNRow();
		int nCol = gameBoard.getNCol();
		
		if (isPencilMarkModeOn) { // input pencil mark
			if (nRow != -1) { // if the user has selected a cell for input
				int cellIndex = nRow * GameVariables.HOUSE_SIZE + nCol; // which cell the user has selected: pencilmarks[cellIndex][]
				int pencilmarkIndex = num - 1; // the position of the pencil mark in the 2d array: pencilmarks[][pencilmarkIndex]
				
				// Update pencil marks
				if (pencilmarks[cellIndex][pencilmarkIndex] == 0)
					pencilmarks[cellIndex][pencilmarkIndex] = num;
				else
					pencilmarks[cellIndex][pencilmarkIndex] = 0;
				
				// Redraw pencil marks
				gameBoard.redrawPencilmarks(pencilmarks);	
				
			}
		}
		else { // input number
			if (clues[nRow * GameVariables.HOUSE_SIZE + nCol] == 0) { // if the selected cell is not solved
				if (nRow != -1) { // if the user has selected a cell for input
					String instrMsg = "";
					if (gamePageUtil.isInputValid(puzzleSolution, num, nRow, nCol)) { // if the input number is valid
						updateCluesAndPencilmarks(nRow, nCol, num);
						
						// Determine which strategy the student is using and update student model
						tutor.matchStudentUsedStrategy(nRow, nCol, expert, student);
						
						// Update the expert model including the Candidate Matrix and the Applicable Strategy Matrix
						expert.updateWorkingMemory(num, gamePageUtil.getTwoDClues(clues));
						
						// Give instruction for valid number assignment
						instrMsg = tutor.getInstrForValidAsg(num, nRow, nCol);
						
						// Set hint click count to zero
						tutor.setHintClickCountToZero();
						
					}
					else {
						instrMsg = tutor.getInstrForInvalidAsg(num, nRow, nCol);
						
					}
					// Output instruction
					outputInstruction(instrMsg);				
					
					// Check if it is the end of the game by checking if the user has solved all cells
					if (gamePageUtil.ifEndOfGame(clues)) {					
						endGame();
					}					
				}
			}		
		}		
	}

	private void updateCluesAndPencilmarks(int nRow, int nCol, int num) {
		// Update clue
		clues[nRow * GameVariables.HOUSE_SIZE + nCol] = num;
		
		// Update pencil marks
		for (int i = 0; i < GameVariables.HOUSE_SIZE; i ++)
			pencilmarks[nRow * GameVariables.HOUSE_SIZE + nCol][i] = 0;
				
		// Redraw clues
		gameBoard.redrawClues(clues);
		
		// Redraw pencil marks
		gameBoard.redrawPencilmarks(pencilmarks);
	}
	
	/**
	 * Get a puzzle with a chosen difficulty level
	 * @param diff		difficulty level of a puzzle
	 */
	private String getPuzzleOfDiff(int diff) {
		String puzzleStr = DEFAULT_PUZZLE;
		if (ifResumeIndicator == RESUME_INDICATOR) // resume a game
			puzzleStr = getPreferences(MODE_PRIVATE).getString(RESUME_PUZZLE, DEFAULT_PUZZLE);
		else // start a new game
			puzzleStr = gamePageUtil.chooseRandomPuzzle(this, diff);
		
		return puzzleStr;
	}
	
	private int[][] getPencilmarks(int diff) {
		int[][] pmarks = new int[GameVariables.PUZZLE_LENGTH][GameVariables.HOUSE_SIZE];
		
		if (ifResumeIndicator == RESUME_INDICATOR) { // resume a game
			String pmStr = getPreferences(MODE_PRIVATE).getString(RESUME_PENCILMARK, "");
			pmarks = gamePageUtil.convertPencilmarkFromStringToIntArray(pmStr);
		}
		else {			
			// Set pencil marks to be all 0
			for (int i=0; i<GameVariables.PUZZLE_LENGTH; i++)
				for (int j=0; j<GameVariables.HOUSE_SIZE; j++)
					pmarks[i][j] = 0;
		}
		
		return pmarks;
	}
	
	/**
	 * Turn pencil mark mode on or off
	 * @param mode		a boolean variable indicating the state of the pencil mark mode
	 * 					if true, pencil mark mode on; else off
	 */
	private void changePencilMarkMode(boolean mode) {
		isPencilMarkModeOn = mode;
	}
	
	/**
	 * Output instructions in instruction box
	 * @param instrMsg		a string containing the instruction message to be output
	 */
	public void outputInstruction(String instrMsg) {
        instructionBox.append(instrMsg);
        
        /*
         * Auto-scrolling TextView in android to bring text into view
         * Code source: http://stackoverflow.com/questions/3506696/auto-scrolling-textview-in-android-to-bring-text-into-view
         * Author: 		KNfLrPn
         * Date: 		September 8, 2011
         * */
        // Find the amount we need to scroll.  This works by asking the TextView's internal layout 
        // for the position of the final line and then subtracting the TextView's height
        if (instructionBox.getLineCount() != 0) { // if the instruction box is not empty
	        final int scrollAmount = instructionBox.getLayout().getLineTop(instructionBox.getLineCount()) - instructionBox.getHeight();
	        // If there is no need to scroll, scrollAmount will be <= 0
	        if(scrollAmount > 0)
	        	instructionBox.scrollTo(0, scrollAmount);
	        else
	        	instructionBox.scrollTo(0,0);
        }
	}
	
	/**
	 * After an answer is given by the system, redraw the game board with the newly given answer and 
	 * 		update the Candidate Matrix and the Applicable Strategy Matrix in the Expert Model
	 * @param nRow		Row index of the cell where the answer is given
	 * @param nCol		Column index of the cell where the answer is given
	 * @param num		The answer
	 */
	public void handleAutoFillAnswer(int nRow, int nCol, int num) {
		updateCluesAndPencilmarks(nRow, nCol, num);
		
		// Check if it is the end of the game by checking if the user has solved all cells
		if (gamePageUtil.ifEndOfGame(clues)) 					
			endGame();	
		else
			// Update the expert model including the Candidate Matrix and the Applicable Strategy Matrix
			expert.updateWorkingMemory(num, gamePageUtil.getTwoDClues(clues));
	}	
	
	/**
	 * Display game timer
	 */
	private void displayTimer(long tUsed) {
		gameStartTime = System.currentTimeMillis() - tUsed;
        timerHandler.removeCallbacks(timerDisplayTask);
        timerHandler.postDelayed(timerDisplayTask, 100);
	}	
	
	/**
	 * Update game timer once a second
	 */
	private void updateDisplayTimer() {
       final long start = gameStartTime;
       timeUsed = System.currentTimeMillis() - start;
       int seconds = (int) (timeUsed / 1000);
       int minutes = seconds / 60;
       seconds     = seconds % 60;

       if (seconds < 10) {    	 
      	   timerDisplayArea.setText("" + minutes + ":0" + seconds);
       } 
       else {
    	   timerDisplayArea.setText("" + minutes + ":" + seconds);            
       }
     
       timerHandler.postDelayed(timerDisplayTask, 1000);
	}
	
	/**
	 * Set a timer for auto-instruction
	 * It will trigger auto-instruction after a certain time interval
	 */
	private void setAutoInstrTimer() {		
		timerHandler.removeCallbacks(autoInstrTask);
		timerHandler.postDelayed(autoInstrTask, autoInstrTimeInterval);
	}
	
	/**
	 * Give auto-instruction
	 */
	private void giveAutoInstr() {
		outputInstruction(tutor.giveHint(this, gamePageUtil.getTwoDClues(clues)));
		setAutoInstrTimer();
	}
	
	/**
	 * Detect if the system has been idle for a certain period of time, in this case SYSTEM_IDLE_THRESHOLD.
	 * If yes, stop auto-instruction
	 */
	private void setSystemIdleDetector() {
		timerHandler.removeCallbacks(stopAutoInstrTask);
		timerHandler.postDelayed(stopAutoInstrTask, SYSTEM_IDLE_THRESHOLD);
	}
	
	/**
	 * Stop auto-instruction
	 */
	private void stopAutoInstr() {
		// Stop auto-instruction task
		timerHandler.removeCallbacks(autoInstrTask);
	}
	
	/**
	 * Get expert variable
	 * @return		this.expert
	 */
	public Expert getExpert() {
		return this.expert;
	}
		
	/**
	 * Get student variable
	 * @return		this.student
	 */
	public Student getStudent() {
		return this.student;
	}	
	
	/**
	 * Remove all timer handler tasks in queue
	 */
	private void removeAllTimeHandlerTasks() {
		timerHandler.removeCallbacks(timerDisplayTask);
		timerHandler.removeCallbacks(autoInstrTask);
		timerHandler.removeCallbacks(stopAutoInstrTask);
	}
	
	/**
	 * Save current student's performance to external MySQL database
	 */
	private void saveToMySQL() {		
		mySQLHandler = new MySQLHandler();
		mySQLHandler.syncDB(student);
	}
	
	/**
	 * Save current student's performance to SQLite database
	 */
	private void saveToSQLite() {
		sqliteHandler = new SQLiteHandler(this);
		studentTable = new StudentTable();
		if (studentTable.updateStudentById(sqliteHandler, student) != 1)
			Log.e("Sudoku", "Error updating student.");
	}
	
	/**
	 * Display congratulation message when game ends
	 */
	private void displayCongratulation() {
		String congratMsg = getResources().getString(R.string.game_end_congrat_msg);
		AlertDialog alertDialog = new AlertDialog.Builder(this).create();		
		alertDialog.setTitle(getResources().getString(R.string.game_end_congrat_title));
		alertDialog.setMessage(congratMsg);
		alertDialog.setButton("OK", new DialogInterface.OnClickListener() {			
			public void onClick(DialogInterface dialog, int which) {
				dialog.dismiss();
				finish();
			}
		});	
		alertDialog.show();	
	}
	
	/**
	 * End the current game
	 */
	private void endGame() {
		// Remove all time handler tasks in queue
		removeAllTimeHandlerTasks();			
		
		// Update student Sudoku experience by adding one to it 
		student.updateSudokuExperience();
		
		// If the current user is a student, save his/her performance to external MySQL
		if (isStudent) 
			saveToMySQL();	
		
		// Congratulate the student by pop-up dialogue
		displayCongratulation();
	}
	
	public GameBoard getGameBoard() {
		return this.gameBoard;
	}
	
	public int[] getClues() {
		return this.clues;
	}
	
}
